package autorization;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;


/**
 * Класс Таблицы Ролей с многопотоковым доступом.
 * Данная таблица представляет собой контейнер строк,
 * которые являются объектами класса Role.
 * Также, учитывается соответствие идентификатора роли и её наименования.
 * Соответствии наименований с идентификаторами устанавливается через
 * две хэш-таблицы с многопоточным доступом
 * @author ID=1008, Ustinov Anton
 */
public class RolesTable implements IRolesTable {

    //--------------------------Константы класса----------------------------------------------------
    static public final String format = "GFDRF 1.0"; //Format of saving Roles table
    static public final String defaultFile = "GFDRolesDefault.txt"; //default name autorization file
    //----------------------------------------------------------------------------------------------
   
    private ConcurrentHashMap<LongComparable,Role> table;
    private ConcurrentHashMap<String,LongComparable> ids;
    
    private Lock readLockFile; //Locking on reading
    private Lock writeLockFile; //Locking on writing
    private String path;

    private volatile boolean IsFileError = false; //Произошла ли в файле ошибка
    //--------------------------- Inner Methoods --------------------------------
    /**
     * To set path to autorization file
     * @param path - path to file. if it equal null-valuee, then autorization information puts into
     * default project folder with name quual class constant "defaultFile". If entry folder is not exist
     * then this folder will create. if entry file is not exits then this file will create. If
     * entry file is exist then information from the file load to memory.
     * @throws java.net.URISyntaxException - syntax error in path
     * @throws java.io.IOException - input-output error
     * @throws autorization.FormatException The file format does not correspond to a format supported by the given version of the program
     */
    private void _InitPath(String path) throws URISyntaxException, IOException, FormatException{
      if(path == null){
       path = ".";
      }
      File file = new File(path);
      //To check up correct format of path
      URI uri = new URI(file.toURI().toString());
      //if path is directory then concatanation to path the default file name
      if(file.isDirectory()){
        if(!file.exists()) file.mkdirs();
        path = file.getPath() + File.separator + defaultFile;
        file = new File(path);
      }
      //If file exist then exit else to create new file
      this.path = path;
      if(!file.exists()){
        file.createNewFile();
        fExport();
      }
       else{
         //Call of import methood
         fImport(path);
       }
    }

    /*------------------------------------------------------*/
    public RolesTable (String path) throws URISyntaxException, IOException, FormatException {
      table = new ConcurrentHashMap<LongComparable,Role>(); 
      ids = new ConcurrentHashMap<String,LongComparable>();
      ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
      readLockFile = lock.readLock();
      writeLockFile = lock.writeLock();

      _InitPath(path);
    }

    /**
     * Добавление новой роли в таблицу.
     * В случае, если роль уже существует - будет выдано исключение RolesTableException.
     * @param R - объект Роль, с указанным именем, приоритетом и списком допустимых
     * действий для данной роли.
     * @throws IOException  - файл сериализации таблицы ролей не доступен или был удалён во время работы программы
     * @throws RolesTableException  - роль с такими атрибутами уже зарегистрирована в системе
    */
    public synchronized void addRole (Role R) throws RolesTableException, IOException {
        if (findRole(R.getName()) != null) throw new RolesTableException();
        table.put(new LongComparable(R.getID()), R);
        ids.put(R.getName(), new LongComparable(R.getID()));
        fExport();
    }
    
    /** Удаление существующей роли из таблицы ролей.
     * @param ID - уникальный идентификатор удаляемой роли
     * в случае, если удаляемой роли не существует, вызывается исключение
     * @throws autorization.RolesTableException
     * @throws IOException  - файл сериализации таблицы ролей не доступен или был удалён во время работы программы
    */
    public synchronized void removeRole (long ID) throws RolesTableException, IOException{
        Role ROLE = findRole(ID);
        if (ROLE != null){
         ids.remove(ROLE.getName());
         table.remove(new LongComparable(ID));
        } else throw new RolesTableException();
        fExport();   
    }

    /** Обновление Роли.
     * @param R - ссылка на объект роли
     * @throws autorization.RolesTableException
     * @throws IOException  - файл сериализации таблицы ролей не доступен или был удалён во время работы программы
    */
    public synchronized void updateRole (Role R) throws RolesTableException, IOException{
        Role ROLE = findRole(R.getID());
        if (ROLE == null) throw new RolesTableException();
        table.put(new LongComparable(ROLE.getID()), R);
        ids.put(R.getName(), new LongComparable(R.getID()));
        fExport();      
    }

     /**
     * Найти роль по идентификатору
     * @param ID - идентификатор роли
     * @return найденная роль или null-значение в случае если роль с таким идентификатором не зарегистрирована в таблице
     */
    public Role findRole (long ID) {
        Role ROLE = table.get(new LongComparable(ID));
        return ROLE;
    }

    /**
    * Найти роль по имени
    * @param Name -имя роли
    * @return найденная роль или null-значение в случае если роль с таким именем не зарегистрирована в таблице
    */
    public Role findRole (String Name) {
        LongComparable lg = ids.get(Name);
        if(lg == null) return null;
        Role ROLE = table.get(lg);
        return ROLE;
    }

    /**
     * Получить директорию файла таблицы ролей
     * @return путь к файлу таблицы ролей
     */
    public String getPath() {
      return path;
    }

    /*------------------------------------------------------*/
    private void fExport () throws IOException  {
       if(IsFileError) throw new IOException();
       Thread thread = new Thread(new Runnable() {
       public void run(){
                try {
                    DataOutputStream stream = null;
                    writeLockFile.lock();                    
                    stream = new DataOutputStream(new FileOutputStream(getPath()));
                    stream.writeUTF(format);
                    stream.writeLong(Role.IDCounter);
                    stream.writeInt(table.size());
                    //Проход по всем ролям таблицы
                    Iterator<Role> iter = table.values().iterator();
                    while(iter.hasNext()){
                      Role temp = iter.next();
                      temp.saveToStream(stream);
                    }
                    //-----------------
                    stream.close();
                    writeLockFile.unlock();
                } catch (IOException ex) {
                  IsFileError = true;
                }
       }
      });

      thread.setPriority(Thread.NORM_PRIORITY-1);
      thread.start();
      if(IsFileError) throw new IOException();
    }
    
    /**
     * Импортирование из файла таблица ролей заданного в качестве входного параметра
     * @param FileName - пусть к файлу с таблицей ролей
     * @throws FormatException - формат файла не соответсвует формату таблицы ролей или файл повреждён
     * @throws FileNotFoundException - файл с таким именем не найден
     * @throws IOException  - файл сериализации таблицы ролей не доступен или был удалён во время работы программы
    */
    public void fImport (String path) throws FormatException,FileNotFoundException, IOException{
       int nTotalSize = 0;
       DataInputStream fin = new DataInputStream(new FileInputStream(path));
       try{      
         readLockFile.lock(); 
         String version = fin.readUTF(); 
         if(!version.equals(format)) throw new FormatException();
         Role.IDCounter = fin.readLong(); 
         nTotalSize = fin.readInt();
         
         table.clear(); 
         ids.clear(); 
         
         Role temp = new Role("noname"); 
         for(int i = 0; i < nTotalSize; i++){
           temp.loadFromStream(fin);
           table.put(new LongComparable(temp.getID()), temp); 
           ids.put(temp.getName(), new LongComparable(temp.getID()) ); 
         }
       }finally{
        fin.close(); 
        readLockFile.unlock();
       }

    }

    /**
     * Проверить, не обнаружила ли система ошибок связанных с недоступностью или повреждённостью файла таблицы ролей
     * @return true - файл не содержит ошибок и регулярно обновляется, false - файл повреждён, данные хранятся в ООП!! Примите меры.
    */
    public boolean isFileError(){return IsFileError;}

    /**
     * Снять флаг ошибки файла. Вызывается после того, как администратор системы выполнил некоторые действия по устранению ошибки
     */
    public void uncheckFileError(){
      IsFileError = false;
    }

    /**
    * очистка таблицы ролей
    */
    public void clear() {
        table.clear();
        ids.clear();
    }

    /**
     * Возвращает итератор по значениям таблицы ролей
     * @return
     */
    public Iterator<Role> getIterator()
    {
        return table.values().iterator();
    }

    /**
     * Количество ролей в таблице
     * @return Количество ролей в таблице
     */
    public long count(){
        return table.size();
    }

    /**
     *
     */
    public boolean admissibleUserAction(long RoleID, long RuleID){
        Role temp = findRole(RoleID);
        if(temp == null) return false;
        return temp.admissibleAction(RuleID);
    }

}